Skip to content

perf(startup): scope orphan reapers to server commands, share one process scan, add opt-out - #411

Merged
debugmcpdev merged 1 commit into
mainfrom
perf/399-reaper-scoping
Aug 22, 2026
Merged

perf(startup): scope orphan reapers to server commands, share one process scan, add opt-out#411
debugmcpdev merged 1 commit into
mainfrom
perf/399-reaper-scoping

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Fixes #399

The two startup orphan reapers were awaited at the top of main(), before commander even parsed argv — so --version, --help, and bad-arg errors all paid the double process scan, startup blocked on it, and the two reapers walked /proc (or ran ps) independently and concurrently over identical data.

Changes

One shared scan — new src/utils/process-scan.ts produces (pid, args) rows once: one bounded /proc walk on Linux, one ps -ww -A on Darwin. Windows deliberately stays one name-filtered CIM query per process name (java.exe / node.exe) — a single unfiltered Win32_Process query would be strictly more expensive than the two filtered ones. Each reaper's platform listers are now thin wrappers applying its marker matcher over the scan rows (~170 lines of duplicated platform code deleted); the existing *-internals tests pass unchanged through the wrappers.

Scoped to server commands, fire-and-forget — new src/utils/startup-janitor.ts orchestrates: one scan feeds both reapers via their existing lister seams. It's kicked (not awaited) from the stdio/sse/http command actions only, so:

  • --version/--help/check-rust-binary never pay the scan;
  • the transport comes up immediately (safe concurrently: reapers only kill processes whose recorded owner pid is dead, never the live server's children);
  • tests/unit/index.test.ts no longer accidentally executes real PowerShell CIM queries three times per run (it previously called real main() with unmocked reapers).

Opt-outMCP_SKIP_ORPHAN_REAPERS=1 skips the process scans entirely (PID-namespaced containers where orphans are impossible). Documented in the setup guide's env table.

Stale session-run sweep (deferred here from #403) — the janitor also removes run-<ts> dirs older than 7 days under the session log base (os.tmpdir()/debug-mcp-server/sessions/…, holding proxy-<id>.log / dap-trace-<id>.ndjson), then prunes emptied session dirs. mtime-based, best-effort: a live session's run dir stays fresh because the proxy logger writes into it.

Not in scope (as the issue flagged): the sync openSync/readSync single-buffer scan rewrite — it would rewrite the fs mock preambles in both internals test suites for a further ~25 ms/12 MB; the shared-scan + off-startup-path change removes the pain first.

Tests (TDD, watched fail first)

  • New process-scan.test.ts: linux NUL-split + numeric-only + bounded reads, darwin ps parsing, win32 per-name queries with one-failure tolerance.
  • New startup-janitor.test.ts: single scan feeds both reapers' listers with correctly matched rows; env opt-out skips everything; reaper rejection logged, never thrown; sweep removes stale run dirs (real temp dirs + utimes), keeps fresh ones, prunes emptied session dirs.
  • index.test.ts: janitor NOT invoked by main() setup alone (i.e. never for --version); invoked exactly once when the stdio action runs.
  • Both reaper behavioral + internals suites pass unchanged.
  • Full npm test green, npm run lint clean.

🤖 Generated with Claude Code

…cess scan, add opt-out

The two orphan reapers ran before argv parsing (even --version paid the
double scan), blocked startup, and walked /proc / ran ps independently
over identical data (issue #399). Now:

- src/utils/process-scan.ts produces (pid, args) rows once — one bounded
  /proc walk on linux, one ps -ww -A on darwin; win32 stays one filtered
  CIM query per process name (unfiltered would cost more). The reapers'
  platform listers are thin wrappers applying their marker matchers.
- src/utils/startup-janitor.ts orchestrates: one scan feeds both reapers
  via their existing lister seams; kicked fire-and-forget from the
  stdio/sse/http command actions only, so the transport comes up
  immediately and non-server invocations never scan. Safe concurrently:
  reapers only kill processes whose recorded owner pid is dead.
- MCP_SKIP_ORPHAN_REAPERS=1 opt-out for PID-namespaced containers.
- The janitor also sweeps stale session run dirs (proxy-*.log /
  dap-trace-*.ndjson older than 7 days) under the tmpdir session log
  base — the follow-through deferred from #403.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.25641% with 23 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/utils/process-scan.ts 83.87% 10 Missing ⚠️
src/utils/startup-janitor.ts 87.69% 8 Missing ⚠️
src/utils/jvm-orphan-reaper.ts 81.81% 2 Missing ⚠️
src/utils/proxy-orphan-reaper.ts 81.81% 2 Missing ⚠️
src/index.ts 85.71% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@debugmcpdev
debugmcpdev merged commit 9189387 into main Aug 22, 2026
9 of 10 checks passed
@debugmcpdev
debugmcpdev deleted the perf/399-reaper-scoping branch August 22, 2026 03:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(startup): scope orphan reapers to server commands, share one /proc walk, allow opt-out

2 participants